using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Collections.Generic;

namespace THSLib
{
    public delegate object GetHandler(object source);
    public delegate void SetHandler(object source, object value);
    public delegate object InstantiateObjectHandler();

    public sealed class DynamicMethodCompiler
    {
        
        private DynamicMethodCompiler() { }

        /// <summary>
        ///  CreateInstantiateObjectDelegate 
        /// </summary>
        ///

        internal static InstantiateObjectHandler CreateInstantiateObjectHandler(Type type)
        {
            ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
            if (constructorInfo == null)
            {
                throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type));
            }

            DynamicMethod dynamicMethod = new DynamicMethod("InstantiateObject", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), null, type, true);
            ILGenerator generator = dynamicMethod.GetILGenerator();
            generator.Emit(OpCodes.Newobj, constructorInfo);
            generator.Emit(OpCodes.Ret);
            return (InstantiateObjectHandler)dynamicMethod.CreateDelegate(typeof(InstantiateObjectHandler));
        }
        /// <summary>
        ///    CreateGetDelegate
        /// </summary>
        ///

        internal static GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo)
        {
            MethodInfo getMethodInfo = propertyInfo.GetGetMethod(true);
            DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
            ILGenerator getGenerator = dynamicGet.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Call, getMethodInfo);
            BoxIfNeeded(getMethodInfo.ReturnType, getGenerator);
            getGenerator.Emit(OpCodes.Ret);

            return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
        }

        
        /// <summary>
        ///    CreateGetDelegate
        /// </summary>
        /// 

        internal static GetHandler CreateGetHandler(Type type, FieldInfo fieldInfo)
        {
            DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
            ILGenerator getGenerator = dynamicGet.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Ldfld, fieldInfo);
            BoxIfNeeded(fieldInfo.FieldType, getGenerator);
            getGenerator.Emit(OpCodes.Ret);

            return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
        }

        /// <summary>
        /// CreateSetDelegate  
        /// </summary>
        /// 

        internal static SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo)
        {
            MethodInfo setMethodInfo = propertyInfo.GetSetMethod(true);
            DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
            ILGenerator setGenerator = dynamicSet.GetILGenerator();

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            UnboxIfNeeded(setMethodInfo.GetParameters()[0].ParameterType, setGenerator);
            setGenerator.Emit(OpCodes.Call, setMethodInfo);
            setGenerator.Emit(OpCodes.Ret);

            return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
        }

      /// <summary>
      ///  CreateSetDelegate
      /// </summary>
      /// 

        internal static SetHandler CreateSetHandler(Type type, FieldInfo fieldInfo)
        {
            DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
            ILGenerator setGenerator = dynamicSet.GetILGenerator();

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            UnboxIfNeeded(fieldInfo.FieldType, setGenerator);
            setGenerator.Emit(OpCodes.Stfld, fieldInfo);
            setGenerator.Emit(OpCodes.Ret);

            return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
        }

        
        /// <summary>
        ///   CreateGetDynamicMethod
        /// </summary>
        /// 

        private static DynamicMethod CreateGetDynamicMethod(Type type)
        {
            return new DynamicMethod("DynamicGet", typeof(object), new Type[] { typeof(object) }, type, true);
        }

        /// <summary>
        ///   CreateGetDynamicMethod
        /// </summary>
        /// 

        private static DynamicMethod CreateSetDynamicMethod(Type type)
        {
            return new DynamicMethod("DynamicSet", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true);
        }

        /// <summary>
        ///   BoxIfNeeded
        /// </summary>
        /// 

        private static void BoxIfNeeded(Type type, ILGenerator generator)
        {
            if (type.IsValueType)
            {
                generator.Emit(OpCodes.Box, type);
            }
        }

       /// <summary>
        ///   BoxIfNeeded
       /// </summary>
       /// 

        private static void UnboxIfNeeded(Type type, ILGenerator generator)
        {
            if (type.IsValueType)
            {
                generator.Emit(OpCodes.Unbox_Any, type);
            }
        }
    }

    public sealed class DynamicInvoker
    {
        public static Hashtable lstGetHandler = new Hashtable();
        public static Hashtable lstSetHandler = new Hashtable();
        public static Hashtable lstInitHandler = new Hashtable();

        public static object DynamicGetValue(object obj, PropertyInfo _propertyInfo)
        {

            try
            {
                string key = _propertyInfo.DeclaringType.Name + _propertyInfo.Name;
                if (lstGetHandler[key] != null)
                {
                    GetHandler getHandler = (GetHandler)lstGetHandler[key];
                    return getHandler(obj);
                }
                else
                {
                    GetHandler getHandler = DynamicMethodCompiler.CreateGetHandler(_propertyInfo.DeclaringType, _propertyInfo);
                    lstGetHandler.Add(key, getHandler);
                    return getHandler(obj);
                }
            }
            catch (System.Exception ex)
            {
                return _propertyInfo.GetValue(obj, null);
            }

        }

        public static void DynamicSetValue(object obj, PropertyInfo _propertyInfo, object value)
        {
            try
            {
                string key = _propertyInfo.DeclaringType.Name + _propertyInfo.Name;
                SetHandler setHandler;
                if (lstSetHandler[key] != null)
                    setHandler = (SetHandler)lstSetHandler[key];
                else
                {
                    setHandler = DynamicMethodCompiler.CreateSetHandler(_propertyInfo.DeclaringType, _propertyInfo);
                    lstSetHandler.Add(key, setHandler);
                }

                setHandler(obj, value);
            }
            catch (System.Exception ex)
            {
                _propertyInfo.SetValue(obj, value, null);
            }


        }

        public static object DynamicCreateInstanceObject(Type typeObj)
        {
            try
            {
                InstantiateObjectHandler instantiateObjectHandler;
                if (lstInitHandler[typeObj.FullName] != null)
                    instantiateObjectHandler = (InstantiateObjectHandler)lstInitHandler[typeObj.FullName];

                else
                {
                    instantiateObjectHandler = DynamicMethodCompiler.CreateInstantiateObjectHandler(typeObj);
                    lstInitHandler.Add(typeObj.FullName, instantiateObjectHandler);
                }

                return instantiateObjectHandler();
            }
            catch (System.Exception ex)
            {
                return typeObj.InvokeMember("", BindingFlags.CreateInstance, null, null, null);
            }


        }
    }

}